package com.ming.common.liteflow.core.exec;

import cn.hutool.core.util.StrUtil;
import com.ming.common.util.number.NumberUtil;
import com.ming.common.liteflow.core.dynamic.IvyDynamicClass;
import com.ming.common.liteflow.core.enums.LiteFlowEnums;
import com.ming.common.liteflow.core.flowexecutor.FlowExecutorUtil;
import com.ming.common.liteflow.core.graph.Node;
import com.ming.common.liteflow.core.node.CmpScriptUtil;
import com.ming.common.liteflow.core.node.IvyCmp;
import com.ming.common.liteflow.core.node.NodeInfoWrapper;
import com.ming.common.liteflow.core.node.SpringBeanUtil;
import com.yomahub.liteflow.builder.LiteFlowNodeBuilder;
import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import com.yomahub.liteflow.flow.LiteflowResponse;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class ELExecUtil {

    public static String exec(String el){
        return exec(FlowExecutorUtil.getFlowExecutor(), el);
    }
    public static String exec(String el,boolean isBuild){
        return exec(FlowExecutorUtil.getFlowExecutor(), el, isBuild);
    }

    public static String exec(IvyCmp info){
        return exec(FlowExecutorUtil.getFlowExecutor(), info, true);
    }
    public static String exec(IvyCmp info,boolean isBuild){
        return exec(FlowExecutorUtil.getFlowExecutor(), info, isBuild);
    }

    public static String exec(FlowExecutor flowExecutor, String el){
        buildComponentIds(el);
        return executor(flowExecutor, el);
    }

    public static String exec(FlowExecutor flowExecutor, String el,boolean isBuild){
        if(isBuild) {
            buildComponentIds(el);
        }
        return executor(flowExecutor, el);
    }

    public static String exec(FlowExecutor flowExecutor, List<IvyCmp> list, List<IvyDynamicClass> dynamicClassList,List<Node> flowNodeList, List<Node> fallbackList, String el){
        buildComponentIds(list,dynamicClassList,flowNodeList,fallbackList,el);
        return executor(flowExecutor, el);
    }

    public static String exec(FlowExecutor flowExecutor, IvyCmp info,boolean isBuild){
        if(isBuild){
            buildComponentIds(info);
        }
        return executor(flowExecutor, info.getEl());
    }

    public static String executor(FlowExecutor flowExecutor, String el){
        String chainId = "chain_test_"+ NumberUtil.getNextIndex();
        LiteFlowChainELBuilder.createChain().setChainId(chainId).setChainName(chainId).setEL(el).build();
        Future<LiteflowResponse> future = flowExecutor.execute2Future(chainId, null, new HashMap<>());
        try {
            System.out.println("-----------------------------exec start-----------------------------");
            LiteflowResponse response = future.get();
            System.out.println("-----------------------------exec end-----------------------------");
            return response.getExecuteStepStrWithTime();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e.getCause());
        }
    }

    public static final String pattern = "node\\(\"([^\"]+)\"\\)";

    public static List<String> getComponentIds(String el){
        Pattern r = Pattern.compile(pattern);
        Matcher m = r.matcher(el);
        List<String> list = new ArrayList<>();
        while (m.find()) {
            list.add(m.group(1));
        }
        return list;
    }

    public static List<String> getComponentIds(IvyCmp info){
        List<String> list = getComponentIds(info.getEl());
        list.remove(info.getComponentId());
        return list;
    }

    public static void buildComponentIds(String el){
        buildComponentIds(getComponentIds(el));
    }

    public static void buildComponentIds(List<IvyCmp> list, List<IvyDynamicClass> dynamicClassList,List<Node> flowNodeList, List<Node> fallbackList, String el){
        buildComponentIds(list,dynamicClassList,flowNodeList,fallbackList,getComponentIds(el));
    }

    public static void buildComponentIds(IvyCmp info){
        buildComponentIds(info,getComponentIds(info));
    }

    public static void buildComponentIds(IvyCmp info,List<String> componentIds){
        for (String componentId : componentIds){
            try {
                SpringBeanUtil.getBean(componentId);
            }catch (Exception e){
                //构建一个脚本组件
                LiteFlowNodeBuilder.createScriptNode().setId(componentId)
                        .setName("组件"+componentId)
                        .setLanguage("java")
                        .setScript("import com.yomahub.liteflow.slot.DefaultContext;\n" +
                                "    import com.yomahub.liteflow.spi.holder.ContextAwareHolder;\n" +
                                "    import com.yomahub.liteflow.script.body.JaninoCommonScriptBody;\n" +
                                "    import com.yomahub.liteflow.script.ScriptExecuteWrap;\n" +
                                "    public class Demo1 implements JaninoCommonScriptBody{\n" +
                                "        public Void body(ScriptExecuteWrap wrap){\n" +
                                "            return null;\n" +
                                "        }\n" +
                                "    }")
                        .build();
            }
        }
    }

    public static void buildComponentIds(List<String> componentIds){
        for (String componentId : componentIds){
            try {
                SpringBeanUtil.getBean(componentId);
            }catch (Exception e){
                //构建一个脚本组件
                LiteFlowNodeBuilder.createScriptNode().setId(componentId)
                        .setName("组件"+componentId)
                        .setLanguage("java")
                        .setScript("import com.yomahub.liteflow.slot.DefaultContext;\n" +
                                "    import com.yomahub.liteflow.spi.holder.ContextAwareHolder;\n" +
                                "    import com.yomahub.liteflow.script.body.JaninoCommonScriptBody;\n" +
                                "    import com.yomahub.liteflow.script.ScriptExecuteWrap;\n" +
                                "    public class Demo1 implements JaninoCommonScriptBody{\n" +
                                "        public Void body(ScriptExecuteWrap wrap){\n" +
                                "            return null;\n" +
                                "        }\n" +
                                "    }")
                        .build();
            }
        }
    }

    public static void buildComponentIds(List<IvyCmp> list, List<IvyDynamicClass> dynamicClassList,List<Node> flowNodeList, List<Node> fallbackList,List<String> componentIds){
        Set<String> typeMap = Arrays.stream(NodeTypeEnum.values()).map(NodeTypeEnum::getCode).collect(Collectors.toSet());
        Map<String, IvyCmp> cmpMap = list.stream().collect(Collectors.toMap(IvyCmp::getComponentId, m -> m));
        Map<Long, IvyCmp> cmpMaps = list.stream().collect(Collectors.toMap(IvyCmp::getId, m -> m));
        Map<String, String> nodeTypeMap = flowNodeList.stream().collect(Collectors.toMap(m -> m.getProperties().getComponentId(), m->{
            if(typeMap.contains(m.getType())){
                return m.getType();
            }
            return m.getProperties().getType();
        }, (o1,o2)->o2));
        Map<String, NodeInfoWrapper> nodeInfoMap = flowNodeList.stream().collect(Collectors.toMap(m -> m.getProperties().getComponentId(), Node::getProperties, (o1,o2)->o2));
        Map<String, IvyDynamicClass> dynamicClassMap = dynamicClassList.stream().collect(Collectors.toMap(m -> m.getClassId(), m -> m));
        Set<String> fallbackSet = new HashSet<>();
        if(fallbackList != null && !fallbackList.isEmpty()){
            for (Node node : fallbackList){
                NodeInfoWrapper prop = node.getProperties();
                Long fallbackId = null;
                if(prop.getFallbackCommonId() != null){
                    fallbackId = prop.getFallbackCommonId();
                }else if(prop.getFallbackSwitchId() != null){
                    fallbackId = prop.getFallbackSwitchId();
                }else if(prop.getFallbackIfId() != null){
                    fallbackId = prop.getFallbackIfId();
                }else if(prop.getFallbackForId() != null){
                    fallbackId = prop.getFallbackForId();
                }else if(prop.getFallbackWhileId() != null){
                    fallbackId = prop.getFallbackWhileId();
                }else if(prop.getFallbackBreakId() != null){
                    fallbackId = prop.getFallbackBreakId();
                }else if(prop.getFallbackIteratorId() != null){
                    fallbackId = prop.getFallbackIteratorId();
                }
                IvyCmp nodeInfo = cmpMaps.get(fallbackId);
                if(nodeInfo.getFallbackId() != null){
                    IvyCmp n = cmpMaps.get(nodeInfo.getFallbackId());
                    fallbackSet.add(n.getType());
                    CmpScriptUtil.createNode(n);
                }else{
                    fallbackSet.add(prop.getFallbackType());
                    nodeInfo.setFallbackType(prop.getFallbackType());
                    CmpScriptUtil.createNode(nodeInfo);
                }
            }
        }
        for (String componentId : componentIds){
            NodeInfoWrapper nodeInfoWrapper = nodeInfoMap.get(componentId);
            if(nodeInfoWrapper == null){
                IvyCmp nodeInfo = cmpMap.get(componentId);
                IvyDynamicClass dynamicClass = dynamicClassMap.values().stream().filter(m -> componentId.equals(m.getSourceCmpId())).findFirst().orElse(null);
                if(dynamicClass != null){
                    Class<?> cmpClass = ClassExecUtil.buildCmp(dynamicClass);
                    nodeInfo.setClazz(cmpClass.getName());
                    try {
                        if(SpringBeanUtil.getBean(componentId) == null){
                            SpringBeanUtil.registerBean(componentId,cmpClass);
                        }
                    }catch (Exception e){
                        SpringBeanUtil.registerBean(componentId,cmpClass);
                    }

                    String cmpDoOpt = nodeInfo.getCmpDoOpt();
                    if(StrUtil.isNotBlank(cmpDoOpt)){
                        CmpScriptUtil.createNode(cmpMap.get(cmpDoOpt));
                    }

                    CmpScriptUtil.createNode(nodeInfo);
                }
                continue;
            }
            if(nodeInfoWrapper != null && StrUtil.isNotBlank(nodeInfoWrapper.getClazz()) && dynamicClassMap.containsKey(nodeInfoWrapper.getClazz())){
                IvyDynamicClass dynamicClass = dynamicClassMap.get(nodeInfoWrapper.getClazz());
                if(dynamicClass != null){
                    Class<?> cmpClass = ClassExecUtil.buildCmp(dynamicClass);
                    nodeInfoWrapper.setClazz(cmpClass.getName());
                    try {
                        if(SpringBeanUtil.getBean(componentId) == null){
                            SpringBeanUtil.registerBean(componentId,cmpClass);
                        }
                    }catch (Exception e){
                        SpringBeanUtil.registerBean(componentId,cmpClass);
                    }
                    CmpScriptUtil.createNode(nodeInfoWrapper);
                }
                continue;
            }

            create(componentId,cmpMap,nodeInfoWrapper,nodeTypeMap,fallbackSet);
        }
    }

    public static void create(String componentId,Map<String, IvyCmp> cmpMap,IvyCmp nodeInfoWrapper,Map<String, String> nodeTypeMap,Set<String> fallbackSet){
        if(cmpMap.containsKey(componentId)){
            CmpScriptUtil.createNode(nodeInfoWrapper);
        }else{
            try {
                SpringBeanUtil.getBean(componentId);
            }catch (Exception e){
                String type = nodeTypeMap.get(componentId);
                if(!fallbackSet.contains(type)){
                    if(nodeInfoWrapper != null){
                        if(StrUtil.isBlank(nodeInfoWrapper.getLanguage())){
                            nodeInfoWrapper.setLanguage("java");
                        }
                        if(StrUtil.isBlank(nodeInfoWrapper.getScript())){
                            switch (nodeInfoWrapper.getType()){
                                case "common" :
                                case "script":
                                    nodeInfoWrapper.setType(NodeTypeEnum.SCRIPT.getCode());
                                    nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.script.getScriptStr());
                                    break;
                                case "switch":
                                case "switch_script":
                                    nodeInfoWrapper.setType(NodeTypeEnum.SWITCH_SCRIPT.getCode());
                                    nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.switch_script.getScriptStr());
                                    break;
                                case "if":
                                case "if_script":
                                    nodeInfoWrapper.setType(NodeTypeEnum.IF_SCRIPT.getCode());
                                    nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.if_script.getScriptStr());
                                    break;
                                case "for":
                                case "for_script":
                                    nodeInfoWrapper.setType(NodeTypeEnum.FOR_SCRIPT.getCode());
                                    nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.for_script.getScriptStr());
                                    break;
                                case "while":
                                case "while_script":
                                    nodeInfoWrapper.setType(NodeTypeEnum.WHILE_SCRIPT.getCode());
                                    nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.while_script.getScriptStr());
                                    break;
                                case "break":
                                case "break_script":
                                    nodeInfoWrapper.setType(NodeTypeEnum.BREAK_SCRIPT.getCode());
                                    nodeInfoWrapper.setScript(LiteFlowEnums.SCRIPT_JAVA.break_script.getScriptStr());
                                    break;
                                case "iterator":
                                default: break;
                            }
                        }
                        CmpScriptUtil.createNode(nodeInfoWrapper);
                    }else{
                        CmpScriptUtil.createNode(componentId);
                    }
                }
            }
        }
    }
}
